﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace MLP
{
    class kNN
    {
        string inputFile, outputFile="", headerLine;
        public int k;
        double[][] DataSet;
        double[,] DistancesX, DistancesY;
        double dme = 2, ENNthreshold; // distance measure exponent
        double[] Outliers;
        int nVect, nAttr;
        bool[] RemainsAfterInstanceSelection;
        bool classification;//
        struct Dist
        {
            public double dx, dy;
            public int vectorNumber;
            public Dist(double dx, double dy, int vectorNumber)
            {
                this.dx = dx;
                this.dy = dy;
                this.vectorNumber = vectorNumber;
            }
        }

        

        public kNN(string inputFile, int k=5, int dme=2)
        {
            this.k=k;
            this.inputFile = inputFile;
            this.dme = dme;
            DataSet = Utils.LoadFile(inputFile, out headerLine);          
            if (headerLine.Contains("Class") || headerLine.Contains("class"))
                classification = true;
            else
                classification = false;
        }

        public kNN(double[][] DataSet, int k = 5, int dme = 2)
        {
            this.k = k;
            this.DataSet= DataSet;
            this.dme = dme;
        }

        public void GetDistances()
        {
            
            nVect = DataSet.GetLength(0);
            nAttr = DataSet.GetLength(1);
            DistancesX = new double[nVect, nVect];
            DistancesY = new double[nVect, nVect];

            for (int v1 = 0; v1 < nVect; v1++)
            {
                DistancesX[v1, v1] = 0;
                DistancesY[v1, v1] = 0;
            }


            if (classification)
            {
                for (int v1 = 0; v1 < nVect; v1++)
                    for (int v2 = 0; v2 < v1; v2++)
                    {
                        double d = 0;
                        for (int a = 0; a < nAttr - 1; a++)
                            d += Math.Pow(DataSet[v1][a] - DataSet[v2][a], dme);

                        d = Math.Pow(d, 1 / dme);
                        DistancesX[v1, v2] = d;
                        DistancesX[v2, v1] = d;
                        DistancesY[v1, v2] = DataSet[v1][nAttr - 1] == DataSet[v2][nAttr - 1] ? 0 : 1;
                        DistancesY[v2, v1] = DistancesY[v1, v2];
                    }
            }
            else
            {
                for (int v1 = 0; v1 < nVect; v1++)
                    for (int v2 = 0; v2 < v1; v2++)
                    {
                        double d = 0;
                        for (int a = 0; a < nAttr - 1; a++)
                            d += Math.Pow(DataSet[v1][a] - DataSet[v2][a], dme);

                        d = Math.Pow(d, 1 / dme);
                        DistancesX[v1, v2] = d;
                        DistancesX[v2, v1] = d;
                        DistancesY[v1, v2] = Math.Abs(DataSet[v1][nAttr - 1] - DataSet[v2][nAttr - 1]);
                        DistancesY[v2, v1] = DistancesY[v1, v2];

                    }
            }


        }







 
        public void ENN(double theta=0.2)
        {

            this.ENNthreshold = theta;
            Outliers = new double[nVect];
            RemainsAfterInstanceSelection = new bool[nVect]; 

            for (int v1 = 0; v1 < nVect; v1++)
            {
                Dist[] dist = new Dist[nVect];
                for (int v2 = 0; v2 < nVect; v2++)
                    dist[v2] = new Dist(DistancesX[v1, v2], DistancesY[v1, v2], v2);

                Array.Sort(dist, delegate(Dist d1, Dist d2) { return d1.dx.CompareTo(d2.dx);});

                Outliers[v1] = 0;
              
                    double sumWdY=0;
                    for (int i = 1; i <= k; i++)
                    {
                        Outliers[v1] += dist[i].dy / (dist[i].dx>0.2 ? dist[i].dx : 0.2);
                        sumWdY += 1/(dist[i].dx > 0.2 ? dist[i].dx : 0.2);
                    }

                    if (classification)
                        RemainsAfterInstanceSelection[v1] = Outliers[v1] / sumWdY < 0.5;  //correct weighted k-NN classification
                    else
                        RemainsAfterInstanceSelection[v1] = Outliers[v1] / sumWdY < theta;  //correct weighted k-NN regression prediction

                 //   GC.Collect();
            }
           
        }



        public void CNN(double theta = 0.2)
        {

            this.ENNthreshold = theta;
            Outliers = new double[nVect];
            RemainsAfterInstanceSelection = new bool[nVect];

            for (int v1 = 0; v1 < nVect; v1++)
            {
                Dist[] dist = new Dist[nVect];
                for (int v2 = 0; v2 < nVect; v2++)
                    dist[v2] = new Dist(DistancesX[v1, v2], DistancesY[v1, v2], v2);

                Array.Sort(dist, delegate(Dist d1, Dist d2) { return d1.dx.CompareTo(d2.dx); });

                Outliers[v1] = 0;

                double sumWdY = 0;
                for (int i = 1; i <= k; i++)
                {
                    Outliers[v1] += dist[i].dy / (dist[i].dx > 0.2 ? dist[i].dx : 0.2);
                    sumWdY += 1 / (dist[i].dx > 0.2 ? dist[i].dx : 0.2);
                }

                if (classification)
                    RemainsAfterInstanceSelection[v1] = Outliers[v1] / sumWdY < 0.5;  //correct weighted k-NN classification
                else
                    RemainsAfterInstanceSelection[v1] = Outliers[v1] / sumWdY < theta;  //good weighted k-NN regression prediction

                //   GC.Collect();
            }

        }



       

        public string SaveDataSetWithOutliers(int what, ref string outputFile)
        {
            this.outputFile = outputFile;
            string fileName2 = System.IO.Path.GetFileName(inputFile);
            string fileName3 = fileName2.Substring(0, fileName2.LastIndexOf("."));
            string directoryName = System.IO.Path.GetDirectoryName(inputFile);

            if (what == 0) // Outliers|| what == 2)
            {
                

               // if (outputFile == "")
                    outputFile = directoryName + "\\" + fileName3 + "_outliers.txt";


                System.IO.StreamWriter sw = new System.IO.StreamWriter(outputFile);
                sw.WriteLine(headerLine + "  outlier");

                for (int v = 0; v < nVect - 1; v++)
                {
                    for (int a = 0; a < nAttr; a++)
                        sw.Write(DataSet[v][a].ToString() + " ");
                    sw.WriteLine(String.Format("{0:0.00000}", Outliers[v]));
                }
                for (int a = 0; a < nAttr; a++)
                    sw.Write(DataSet[nVect - 1][a].ToString() + " ");
                sw.Write(String.Format("{0:0.00000}", Outliers[nVect - 1]));
                sw.Close();
            }

            if (what == 1) //ENN || what == 2)
            {
                outputFile = directoryName + "\\" + fileName3 + "_ENN.txt";
                System.IO.StreamWriter sw = new System.IO.StreamWriter(outputFile);

                sw.WriteLine(headerLine);

                for (int v = 0; v < nVect - 1; v++)
                {
                    if (RemainsAfterInstanceSelection[v])
                    {
                        for (int a = 0; a < nAttr; a++)
                            sw.Write(DataSet[v][a].ToString() + " ");
                      //  sw.WriteLine(String.Format("{0:0.00000}", Outliers[v]));
                        sw.WriteLine();
                    }
                }
                if (RemainsAfterInstanceSelection[nVect - 1])
                {
                    for (int a = 0; a < nAttr; a++)
                        sw.Write(DataSet[nVect - 1][a].ToString() + " ");
                  //  sw.Write(String.Format("{0:0.00000}", Outliers[nVect - 1]));
                }
                sw.Close();
            }

            return outputFile;
            
        }



        public string SaveDataSetWithOutliers2(int what, string outputFile, string headerLine)
        {
            //0: outliers, 1:ENN, 2:both

            if (what == 0 || what == 2)
            {

                System.IO.StreamWriter sw = new System.IO.StreamWriter(outputFile);
                sw.WriteLine(headerLine + "  outlier");
                for (int v = 0; v < nVect - 1; v++)
                {
                    for (int a = 0; a < nAttr; a++)
                        sw.Write(DataSet[v][a].ToString() + " ");
                    sw.WriteLine(String.Format("{0:0.00000}", Outliers[v]));
                }
                for (int a = 0; a < nAttr; a++)
                    sw.Write(DataSet[nVect - 1][a].ToString() + " ");
                sw.Write(String.Format("{0:0.00000}", Outliers[nVect - 1]));
                sw.Close();
            }

            if (what == 1 || what == 2)
            {
               // outputFile = directoryName + "\\" + fileName3 + "_ENN.txt";
                System.IO.StreamWriter sw = new System.IO.StreamWriter(outputFile);
                sw.WriteLine(headerLine);
                for (int v = 0; v < nVect - 1; v++)
                {
                    if (RemainsAfterInstanceSelection[v])
                    {
                        for (int a = 0; a < nAttr; a++)
                            sw.Write(DataSet[v][a].ToString() + " ");
                        sw.WriteLine(String.Format("{0:0.00000}", Outliers[v]));
                    }
                }
                if (RemainsAfterInstanceSelection[nVect - 1])
                {
                    for (int a = 0; a < nAttr; a++)
                        sw.Write(DataSet[nVect - 1][a].ToString() + " ");
                    sw.Write(String.Format("{0:0.00000}", Outliers[nVect - 1]));
                }
                sw.Close();
            }

            return outputFile;

        }


    }
}
